jqkit.css = {};

/**
 * Gets the cssText from a CSSRule object cross-browserly.
 * @param {CSSRule} cssRule A CSSRule.
 * @return {string} cssText The text for the rule, including the selector.
 */
jqkit.css.getTextFromRule = function(cssRule) {
	var cssText = '';
	if (cssRule.cssText) {
		// W3C.
		cssText = cssRule.cssText;
	} else if (cssRule.style && cssRule.style.cssText && cssRule.selectorText) {
		// IE: The spacing here is intended to make the result consistent with FF and Webkit.
		var styleCssText = cssRule.style.cssText;
		var thisCssText = cssRule.selectorText + ' { ' + styleCssText + ' }';
		cssText = thisCssText;
	}
	return cssText;
};

/**
 * Returns the CSSRules from a styleSheet.
 * Worth noting here is that IE and FF differ in terms of what they will return. Firefox will return styleSheet.cssRules,
 * which includes ImportRules and anything which implements the CSSRules interface. IE returns simply a list of CSSRules.
 * @param {CSSStyleSheet} styleSheet The CSSStyleSheet.
 * @throws {Error} If we cannot access the rules on a stylesheet object - this can  happen if a stylesheet object's rules
 * are accessed before the rules have been downloaded and parsed and are "ready".
 * @return {CSSRuleList} An array of CSSRules or null.
 */
jqkit.css.getRulesFromStyleSheet = function(styleSheet) {
	var cssRuleList = null;
	try {
		// IE is .rules, W3C is cssRules.
		cssRuleList = styleSheet.rules || styleSheet.cssRules;
	} catch (e) {
		// This can happen if we try to access the css object model before it is "ready".
		if (e.code == 15) {
			// Firefox throws an NS_ERROR_DOM_INVALID_ACCESS_ERR error if a stylesheet
			// is read before it has been fully parsed. Let the caller know which
			// stylesheet failed.
			e.styleSheet = styleSheet;
			throw e;
		}
	}
	return cssRuleList;
};

/**
 * Cross browser function to add a CSSRule into a CSSStyleSheet, optionally at a given index.
 * Note: Inserting at index 0 fails on Firefox 2 and 3 with an exception warning "Node cannot be inserted at the 
 * specified point in the hierarchy."
 * @param {CSSStyleSheet} cssStyleSheet The CSSRule's parentStyleSheet.
 * @param {string} cssText The text for the new CSSRule.
 * @param {number} opt_index The index of the cssRule in its parentStylesheet.
 * @throws {Error} If the css rule text appears to be ill-formatted.
 * @return {number} The index at which the rule was inserted.
 */
jqkit.css.addRule = function(cssStyleSheet, cssText, opt_index) {
	var index = opt_index;
	if (index < 0 || index === undefined) {
		// If no index specified, insert at the end of the current list of rules.
		// If on IE, use rules property, otherwise use cssRules property.
		var rules = cssStyleSheet.rules || cssStyleSheet.cssRules;
		index = rules.length;
	}
	if (cssStyleSheet.insertRule) {
		// W3C.
		cssStyleSheet.insertRule(cssText, index);
	} else {
		// IE: We have to parse the cssRule text to get the selector separated from the style text.
		// aka Everything that isn't a colon, followed by a colon, then the rest is the style part.
		var matches = /^([^\{]+)\{([^\{]+)\}/.exec(cssText);
		if (matches.length == 3) {
			var selector = matches[1];
			var style = matches[2];
			cssStyleSheet.addRule(selector, style, index);
		} else {
			throw Error('Your CSSRule appears to be ill-formatted.');
		}
	}
	return index;
};

/**
 * Cross browser function to remove a CSSRule in a CSSStyleSheet at an index.
 * @param {CSSStyleSheet} cssStyleSheet The CSSRule's parentStyleSheet.
 * @param {number} index The CSSRule's index in the parentStyleSheet.
 */
jqkit.css.removeRule = function(cssStyleSheet, index) {
	if (cssStyleSheet.deleteRule) {
		// W3C.
		cssStyleSheet.deleteRule(index);
	} else {
		// IE.
		cssStyleSheet.removeRule(index);
	}
};

/**
 * Appends a DOM node to HEAD containing the css text that is passed in.
 * @param {string} cssText CSS to add to the end of the document.
 * @param {string} opt_id ID attribute of the stylesheet to create.
 * @return {element} The newly created STYLE element.
 */
jqkit.css.createStyleSheet = function(cssText, opt_id) {
	if (!cssText) {
		cssText = '';
	}
	if (!opt_id) {
		opt_id = '';
	}
	var styleNode = document.createElement('style');
	styleNode.setAttribute('type', 'text/css');
	styleNode.setAttribute('id', opt_id);
	var head = document.getElementsByTagName('head')[0];
	head.appendChild(styleNode);
	if (styleNode.styleSheet) {
		// IE.
		styleNode.styleSheet.cssText = cssText;
	} else {
		// W3C.
		styleNode.appendChild(document.createTextNode(cssText));
	}
	return styleNode;
};

/**
 * Removes a style or link tag by id.
 * @param {string} id The id of the tag.
 */
jqkit.css.removeStyleSheet = function(id) {
	var existing = document.getElementById(id);
	if (existing) {
		existing.parentNode.removeChild(existing);
	}
};

/**
 * Dynamically swaps an existing stylesheet reference for a new one. Note: Possible "blinking" affect may occur
 * while new style sheet is retrieved.
 * @param {string} id The id of an existing link tag to remove.
 * @param {string} url The href of the new stylesheet to include.
 */
jqkit.css.swapStyleSheet = function(id, url) {
	var doc = document;
	jqkit.css.removeStyleSheet(id);
	var ss = doc.createElement("link");
	ss.setAttribute("rel", "stylesheet");
	ss.setAttribute("type", "text/css");
	ss.setAttribute("id", id);
	ss.setAttribute("href", url);
	doc.getElementsByTagName("head")[0].appendChild(ss);
};

/**
 * Replaces an existing style sheet's rules. Note: Removes the existing style sheet after appending the new
 * style sheet to try and avoid any possible "blinking" affects.
 * @param {string} id The id of an existing link tag to remove.
 * @param {string} cssText The rules which will replace all the existing rules.
 */
jqkit.css.replaceRulesInStyleSheet = function(id, cssText) {
	var sheet = document.getElementById(id);
	if (!sheet) {
		jqkit.css.createStyleSheet(cssText, id);
	} else {
		var newId = '_' + id;
		sheet.setAttribute('id', newId);
		jqkit.css.createStyleSheet(cssText, id);
		jqkit.css.removeStyleSheet(newId);
	}
};

/**
 * Retrieves all desired css rules that currently apply to the element given.
 * @param {element} element The element of which to retrieve applied css style rules from.
 * @param {array<string>} opt_whiteList Style rules to include in the result.
 * @param {array<string>} opt_blackList Style rules to exclude from the result.
 * @return {object} Object containing style rules (example { 'background-color': 'blue', ... }).
 */
jqkit.css.getElementStyleObject = function(element, opt_whiteList, opt_blackList) {
    var style, rules = {};

    opt_whiteList = opt_whiteList || null;
    opt_blackList = opt_blackList || null;

    function checkWhiteList(key) {
        if (opt_whiteList === null) {
            return true;
        }
        for (var i = 0, length = opt_whiteList.length; i < length; i++) {
            if (opt_whiteList[i] === key) {
                return true;
            }
        }
        return false;
    }

    function checkBlackList(key) {
        if (opt_blackList === null) {
            return true;
        }
        for (var i = 0, length = opt_blackList.length; i < length; i++) {
            if (opt_blackList[i] === key) {
                return false;
            }
        }
        return true;
    }

    if (window.getComputedStyle) {
        style = window.getComputedStyle(element, null);
        for (var i = 0, length = style.length; i < length; i++) {
            var prop = style[i];
            if (checkWhiteList(prop) === true && checkBlackList(prop) === true) {
                rules[prop] = style.getPropertyValue(prop);
            }
        }
    } else if (element.currentStyle) {
        style = dom.currentStyle;
        for (var prop in style) {
            if (checkWhiteList(prop) === true && checkBlackList(prop) === true) {
                rules[prop] = style[prop];
            }
        }
    }

    style = null;
    return rules;
};

/**
 * Retrieves all desired css rules that currently apply to the element given.
 * @param {element} element The element of which to retrieve applied css style rules from.
 * @param {array<string>} opt_whiteList Style rules to include in the result.
 * @param {array<string>} opt_blackList Style rules to exclude from the result.
 * @return {string} String containing style rules.
 */
jqkit.css.getElementStyleText = function(element, opt_whiteList, opt_blackList) {
    var obj = jqkit.css.getElementStyleObject(element, opt_whiteList, opt_blackList);
    var text = '';
    for (var prop in obj) {
        text += prop + ':' + obj[prop] + ';';
    }
    return text;
};